#include "fs.h"
#include "memory.h"

typedef struct {
  char *name;
  size_t size;
  off_t disk_offset;
  off_t open_offset;
} Finfo;

enum {FD_STDIN, FD_STDOUT, FD_STDERR, FD_FB, FD_EVENTS, FD_DISPINFO, FD_NORMAL};

/* This is the information about all files in disk. */
static Finfo file_table[] __attribute__((used)) = {
  {"stdin (note that this is not the actual stdin)", 0, 0},
  {"stdout (note that this is not the actual stdout)", 0, 0},
  {"stderr (note that this is not the actual stderr)", 0, 0},
  [FD_FB] = {"/dev/fb", 0, 0},
  [FD_EVENTS] = {"/dev/events", 0, 0},
  [FD_DISPINFO] = {"/proc/dispinfo", 128, 0},
#include "files.h"
};

#define NR_FILES (sizeof(file_table) / sizeof(file_table[0]))

void init_fs() {
  file_table[FD_FB].size = _screen.height * _screen.width * 4;
}

static int find_file(const char *pathname) {
  for (int fd = 0; fd < NR_FILES; fd++)
  {
    if (file_table[fd].name && 
      strcmp(file_table[fd].name, pathname) == 0) {
      return fd;
    }
  }
  return -1;
}

int fs_open(const char *pathname, int flags, int mode)
{
  int fd = find_file(pathname);
  // Log("Opening %s, fd=%d, size=%d, disk_offset=0x%x", 
  //     pathname, fd, file_table[fd].size, file_table[fd].disk_offset);
  assert(fd != -1); 
  file_table[fd].open_offset = 0; 
  return fd;
}

extern void ramdisk_write(const void *buf, off_t offset, size_t len);
extern void dispinfo_read(void *buf, off_t offset, size_t len);
extern size_t events_read(void *buf, size_t len);

ssize_t fs_read(int fd, void *buf, size_t len)
{
  if (fd < 0 || fd >= NR_FILES) return -1;
  if (fd == FD_EVENTS)
  {
    len = events_read(buf, len);
    return len;
  }
  if (fd == FD_STDIN || fd == FD_STDOUT || fd == FD_STDERR) return 0;
  
  Finfo *file = &file_table[fd];
  size_t available = file->size - file->open_offset;
  size_t actual_len = (len < available) ? len : available;
  if (fd == FD_DISPINFO)
  {
    dispinfo_read(buf, file->open_offset, actual_len);
    file->open_offset += actual_len;
    return actual_len;
  }
  ramdisk_read(buf, file->disk_offset + file->open_offset, actual_len);
  file->open_offset += actual_len;
  return actual_len;
}

int fs_close(int fd)
{
  return 0;
}

size_t fs_filesz(int fd)
{
  return file_table[fd].size;
}

extern void fb_write(const void *buf, off_t offset, size_t len);

ssize_t fs_write(int fd, void *buf, size_t len)
{
  if (fd < 0 || fd >= NR_FILES) return -1;
  Finfo *file = &file_table[fd];
  if (fd == FD_STDOUT || fd == FD_STDERR) 
  {
    const char *p = buf;
    for (size_t i = 0; i < len; i++) _putc(p[i]);
    return len;
  }
  if (fd == FD_STDIN) return 0;
  size_t available = file->size - file->open_offset;
  size_t actual_len = (len < available) ? len : available;
  if (fd == FD_FB)
  {
    fb_write(buf, file->open_offset, actual_len);
    file->open_offset += actual_len;
    return actual_len;
  }
  ramdisk_write(buf, file->disk_offset + file->open_offset, actual_len);
  file->open_offset += actual_len;
  return actual_len;
}

off_t fs_lseek(int fd, off_t offset, int whence)
{
  if (fd < 0 || fd >= NR_FILES) return -1;
  Finfo *file = &file_table[fd];
  switch (whence) {
    case SEEK_SET:
    {
      if(offset >= 0 && offset <= file->size)
      {
        file->open_offset = offset;
        return offset;
      }
    }    
    break;
    case SEEK_CUR:
    {
      if(file->open_offset + offset <= file->size)
      {
        file->open_offset += offset;
        return file->open_offset;
      }
    }
    break;
    case SEEK_END:
    {
      file->open_offset = file->size + offset;
      return file->open_offset;
      break;
    }
  }
  return -1;
}

uintptr_t fs_brk(uintptr_t new_brk) {
    return mm_brk(new_brk);
}